import React, { useCallback } from 'react'
import {
  ReactFlowProvider,
  ReactFlow,
  MiniMap,
  Controls,
  Background,
  useNodesState,
  useEdgesState,
  addEdge,
  useReactFlow,
  ConnectionMode,
  getConnectedEdges,
} from '@xyflow/react'
import TwoPointNode from '@/components/TowPointNode'
import { CustomConnectionLine } from '@/components/CustomConnectionLine'
import {
  generateorXY,
  stabilizeDrawingPath,
  convertUserPathToOrthogonalPreview,
  calculateOrthogonalPathPoints,
} from './utils'
import * as d3 from 'd3'
import '@xyflow/react/dist/style.css'

const nodeTypes = {
  'two-point': TwoPointNode,
}
const initialNodes = [
  {
    id: 'n1',
    position: { x: 0, y: 0 },
    data: { label: 'Node 1' },
    type: 'input',
  },
  {
    id: 'n2',
    position: { x: 200, y: 200 },
    data: { label: 'Node 2' },
    type: 'default',
  },
  {
    id: 'n3',
    position: { x: 0, y: 300 },
    data: { label: 'Node 3' },
    type: 'output',
  },
  // {
  //   id: 'n4',
  //   position: { x: 0, y: 400 },
  //   data: { label: 'Node 4' },
  //   type: 'two-point',
  // },
]

let isDrawing = false
// let hasValidPoint = false
let lineData = []
let drawLineData = []
let connectionPoints = []
let linePath = null
const lineGenerator = d3
  .line()
  .x((d) => d[0])
  .y((d) => d[1])
  .curve(d3.curveLinear) // Or d3.curveBasis for a smoother line
let nodeDraggingStartPoint = {}
let newData = {}

function CustomEdge({ data, source, target, sourceY, targetY }) {
  const sourceIsTop = sourceY < targetY
  if (!data.dirs || !(source in data.dirs || target in data.dirs)) {
    console.log(sourceIsTop)
    data.dirs = {
      [source]: sourceIsTop ? 'top' : 'bottom',
      [target]: sourceIsTop ? 'bottom' : 'top',
    }
  }
  return (
    <path d={lineGenerator(data.drawLineData)} fill="none" strokeWidth="2" stroke="blue"></path>
  )
}

function CustomEdgeTwo({ data, targetX, targetY, sourcePosition, targetPosition }) {
  const lastPathPoint = generateorXY(data.drawLineData[data.drawLineData.length - 1])

  console.log(targetX, targetY)

  // 计算最后一个点到目标点的距离
  const distanceToTarget = Math.sqrt(
    Math.pow(lastPathPoint.x - targetX, 2) + Math.pow(lastPathPoint.y - targetY, 2)
  )

  // 如果距离目标点较远，添加连接线段到目标点
  if (distanceToTarget > 5) {
    // 计算从最后一个路径点到目标点的正交连接
    connectionPoints = calculateOrthogonalPathPoints(
      lastPathPoint.x,
      lastPathPoint.y,
      targetX,
      targetY,
      sourcePosition, // 这里可能需要根据实际情况调整
      targetPosition,
      {
        minDistance: 25,
        offset: 35,
        cornerRadius: 0,
      }
    )
    // 移除连接点的第一个点（与previewPathPoints的最后一个点重复）
    if (connectionPoints.length > 1) {
      connectionPoints.shift()
    }
  }

  return (
    <path
      d={lineGenerator([...data.drawLineData, ...connectionPoints])}
      fill="none"
      strokeWidth="2"
      stroke="blue"
    ></path>
  )
}
const edgeTypes = {
  custom: CustomEdge,
  'custom-two': CustomEdgeTwo,
  custom2: CustomEdge,
}

function getDir(startPoint, endPoint) {
  const endStartOffsetX = endPoint[0] - startPoint[0]
  const endStartOffsetY = endPoint[1] - startPoint[1]
  if (endStartOffsetX >= 0 && endStartOffsetY > 0) {
    return 'right-bottom'
  } else if (endStartOffsetX >= 0 && endStartOffsetY <= 0) {
    return 'right-top'
  } else if (endStartOffsetX < 0 && endStartOffsetY > 0) {
    return 'left-bottom'
  } else if (endStartOffsetX < 0 && endStartOffsetY <= 0) {
    return 'left-top'
  }
}

function getTurningPoint(startPoint, endPoint) {
  const dir = getDir(startPoint, endPoint)
  const turningPoint1 = [endPoint[0], startPoint[1]]
  const turningPoint2 = [startPoint[0], endPoint[1]]
  const w = Math.abs(endPoint[0] - startPoint[0])
  const h = Math.abs(endPoint[1] - startPoint[1])
  // 转折点
  const turningPoint =
    dir == 'right-top' || dir == 'left-bottom'
      ? w > h
        ? turningPoint1
        : turningPoint2
      : w > h
      ? turningPoint2
      : turningPoint1

  return turningPoint
}
/**
 * 1 右上和左下 vs 右下和左上
 * 2 多个edge
 * 3 拖拽node第二次
 */
/**
 * React Flow 的所有内容都存在于视口 (Viewport) 中。
 * 视口包含 x、y 和缩放值。拖动窗格时，会更改 x 和 y 坐标；放大或缩小时，缩放级别也会随之改变。
 * @returns
 */
export function Flow({ type = 'default' }) {
  const [nodes, , onNodesChange] = useNodesState(initialNodes)
  const [edges, setEdges, onEdgesChange] = useEdgesState([])
  const reactFlowIns = useReactFlow()
  /**
   *
   */
  const onConnectStart = () => {
    drawLineData = []
    isDrawing = true
  }
  // console.log(nodes);
  const onConnect = useCallback(
    (params) =>
      setEdges((edgesSnapshot) => {
        const extraOptions = {}
        if (type == 'default') {
          extraOptions['pathOptions'] = { curvature: 0.9 }
        } else if (type == 'smoothstep') {
          extraOptions['pathOptions'] = { offset: 20, borderRadius: 5 }
        }
        const edgeData = {
          drawLineData: drawLineData,
          rawDrawLineData: drawLineData.slice(0),
          firstLineData: drawLineData[0],
          lastLineData: drawLineData[drawLineData.length - 1],
        }
        edgeData.dir = getDir(edgeData.firstLineData, edgeData.lastLineData)
        return addEdge(
          {
            ...params,
            id: `${params.source}-${params.target}-${Date.now()}`,
            type,
            zIndex: 1001,
            data: edgeData,
            ...extraOptions,
          },
          edgesSnapshot
        )
      }),
    [setEdges, type]
  )
  const onConnectEnd = useCallback(() => {
    console.log('end')
    lineData.length = 0
    connectionPoints.length = 0
    linePath = null
    isDrawing = false
  }, [])

  const handleMouseMove = useCallback(
    (event) => {
      if (isDrawing && (type == 'custom' || type == 'custom2')) {
        if (linePath == null) {
          linePath = d3.select('.react-flow__connectionline').select('#test1')
        }
        if (lineData.length == 0) {
          linePath.datum(function () {
            lineData.push([Number(this.dataset.fromx), Number(this.dataset.fromy)])
            // return lineData
          })
        }

        const currentPoint = reactFlowIns.screenToFlowPosition(generateorXY(d3.pointer(event)))

        const position = [currentPoint.x, currentPoint.y]
        const lastRawPoint = lineData[lineData.length - 1]

        // 只有当移动距离足够大时才添加新点
        const distance = Math.sqrt(
          Math.pow(position[0] - lastRawPoint[0], 2) + Math.pow(position[1] - lastRawPoint[1], 2)
        )
        if (distance > 3) {
          lineData.push(position)

          const stabilizeData = stabilizeDrawingPath(lineData)
          drawLineData = convertUserPathToOrthogonalPreview(
            stabilizeData,
            lineData[0][0],
            lineData[0][1]
          )

          linePath.datum(drawLineData)
        }
        if (linePath.attr('data-connstatus') == 'valid') {
          const dataset = linePath.node().dataset
          if (dataset.toy >= dataset.tonode) {
            // handle方向
            if (type == 'custom') {
              // 左右
              drawLineData.push([drawLineData[drawLineData.length - 1][0], Number(dataset.toy)])
            }
            if (type == 'custom2') {
              // 上下
              drawLineData.push([Number(dataset.tox), drawLineData[drawLineData.length - 1][1]])
            }
            if (drawLineData) drawLineData.push([Number(dataset.tox), Number(dataset.toy)])
          }
        }
        drawLineData.length > 0 && linePath.attr('d', lineGenerator)
      }
    },
    [reactFlowIns, type]
  )

  const handleNodeDragStart = useCallback(
    (event, node) => {
      nodeDraggingStartPoint = reactFlowIns.screenToFlowPosition(generateorXY(d3.pointer(event)))
      const connectedEdges = getConnectedEdges(nodes, edges)
      connectedEdges
        .filter((edge) => {
          return edge.source == node.id || edge.target == node.id
        })
        .forEach((edge) => {
          newData[edge.id] = []
        })
      console.log('start', JSON.stringify(newData))
    },
    [reactFlowIns, nodes, edges]
  )
  /**
   * 当前node是
   * 正方向node
   * 反方向node
   */
  const handleNodeDrag = useCallback(
    (event, node) => {
      if (node.dragging) {
        const nodeCurentPoint = reactFlowIns.screenToFlowPosition(generateorXY(d3.pointer(event)))
        const offsetX = nodeCurentPoint.x - nodeDraggingStartPoint.x
        const offsetY = nodeCurentPoint.y - nodeDraggingStartPoint.y

        const connectedEdges = getConnectedEdges(nodes, edges)
        connectedEdges
          .filter((edge) => edge.source == node.id || edge.target == node.id)
          .forEach((edge) => {
            const otherNode = nodes.find((node_) => {
              const nodeIsSource = node.id == edge.source
              if (nodeIsSource) {
                return node_.id == edge.target
              } else {
                return node_.id == edge.source
              }
            })
            newData[edge.id].length = 0
            const edgepath = d3.select(`[data-id=${edge.id}]`).select('path')
            const drawLineData = edge.data.drawLineData
            const edgeDir = edge.data.dir
            const edgeDirs = edge.data.dirs
            console.log(edge, node, otherNode)

            let startPoint = [] // 起点
            let endPoint = [] // 终点
            let turningPoint = [] // 转折点
            if (edgeDir.includes('bottom')) {
              if (edgeDirs[node.id] == 'bottom') {
                startPoint = edge.data.lastLineData
                endPoint = [
                  drawLineData[drawLineData.length - 1][0] + offsetX,
                  drawLineData[drawLineData.length - 1][1] + offsetY,
                ] // 终点
                turningPoint = getTurningPoint(startPoint, endPoint)

                newData[edge.id].push(turningPoint)
                newData[edge.id].push(endPoint)
                edgepath.datum(function () {
                  const lastData = edge.data.rawDrawLineData.concat(newData[edge.id])
                  const lastData2 = edge.data.drawLineData.concat(newData[edge.id])
                  if (lastData.length == lastData2.length) {
                    return lastData
                  }
                  return edge.data.drawLineData.slice(0, 2).concat(lastData)
                })
              }
              if (edgeDirs[node.id] == 'top') {
                startPoint = [drawLineData[0][0] + offsetX, drawLineData[0][1] + offsetY]
                endPoint = edge.data.firstLineData // 起点
                turningPoint = getTurningPoint(startPoint, endPoint)
                newData[edge.id].push(startPoint)
                newData[edge.id].push(turningPoint)
                edgepath.datum(function () {
                  const lastData = newData[edge.id].concat(edge.data.rawDrawLineData)
                  const lastData2 = newData[edge.id].concat(edge.data.drawLineData)
                  if (lastData.length == lastData2.length) {
                    return lastData
                  }
                  return lastData.concat(edge.data.drawLineData.slice(-2))
                })
              }
            }
            if (edgeDir.includes('top')) {
              if (edgeDirs[node.id] == 'top') {
                startPoint = edge.data.lastLineData
                endPoint = [
                  drawLineData[drawLineData.length - 1][0] + offsetX,
                  drawLineData[drawLineData.length - 1][1] + offsetY,
                ] // 终点
                turningPoint = getTurningPoint(startPoint, endPoint)

                newData[edge.id].push(turningPoint)
                newData[edge.id].push(endPoint)
                edgepath.datum(function () {
                  const lastData = edge.data.rawDrawLineData.concat(newData[edge.id])
                  const lastData2 = edge.data.drawLineData.concat(newData[edge.id])
                  if (lastData.length == lastData2.length) {
                    return lastData
                  }
                  return edge.data.drawLineData.slice(0, 2).concat(lastData)
                })
              }
              if (edgeDirs[node.id] == 'bottom') {
                startPoint = [drawLineData[0][0] + offsetX, drawLineData[0][1] + offsetY]
                endPoint = edge.data.firstLineData // 起点
                turningPoint = getTurningPoint(startPoint, endPoint)
                newData[edge.id].push(startPoint)
                newData[edge.id].push(turningPoint)
                edgepath.datum(function () {
                  const lastData = newData[edge.id].concat(edge.data.rawDrawLineData)
                  const lastData2 = newData[edge.id].concat(edge.data.drawLineData)
                  if (lastData.length == lastData2.length) {
                    return lastData
                  }
                  return lastData.concat(edge.data.drawLineData.slice(-2))
                })
              }
            }

            setTimeout(() => {
              edgepath.attr('d', lineGenerator)
            }, 100)
          })
      }
    },
    [nodes, edges, reactFlowIns]
  )

  const handleNodeDragStop = useCallback(
    (event, node) => {
      const connectedEdges = getConnectedEdges(nodes, edges)

      connectedEdges
        .filter((edge) => edge.source == node.id || edge.target == node.id)
        .forEach((edge) => {
          const edgeDir = edge.data.dir
          const edgeDirs = edge.data.dirs
          // newData[edge.id].forEach((i) => {
          //   d3.select(`[data-id=${edge.id}]`)
          //     .append('circle')
          //     .attr('cx', i[0])
          //     .attr('cy', i[1])
          //     .attr('r', 2)
          // })
          if (edgeDir.includes('bottom')) {
            if (edgeDirs[node.id] == 'bottom') {
              edge.data.drawLineData.push(...newData[edge.id])
            }
            if (edgeDirs[node.id] == 'top') {
              edge.data.drawLineData.unshift(...newData[edge.id])
            }
          }
          if (edgeDir.includes('top')) {
            if (edgeDirs[node.id] == 'top') {
              edge.data.drawLineData.push(...newData[edge.id])
            }
            if (edgeDirs[node.id] == 'bottom') {
              edge.data.drawLineData.unshift(...newData[edge.id])
            }
          }
        })
    },
    [nodes, edges]
  )

  const allowAnyConnection = () => {
    // console.log(connection)

    return true
  }

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <ReactFlow
        connectionMode={ConnectionMode.Strict} // Loose
        nodes={nodes}
        edges={edges}
        nodeTypes={nodeTypes}
        edgeTypes={edgeTypes}
        colorMode="dark"
        isValidConnection={allowAnyConnection}
        connectionLineComponent={type == 'custom' || type == 'custom2' ? CustomConnectionLine : ''}
        fitView
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnectStart={onConnectStart}
        onConnect={onConnect}
        onConnectEnd={onConnectEnd}
        onPaneMouseMove={handleMouseMove}
        onNodeDragStart={handleNodeDragStart}
        onNodeDrag={handleNodeDrag}
        onNodeDragStop={handleNodeDragStop}
      >
        <Controls />
        <MiniMap nodeStrokeWidth={3} zoomable pannable />
        <Background variant="dots" gap={12} size={1} />
      </ReactFlow>
    </div>
  )
}

export default function App(props) {
  return (
    <ReactFlowProvider>
      <Flow {...props} />
    </ReactFlowProvider>
  )
}
